﻿Imports Aricie.DNN.ComponentModel
Imports System.Globalization
Imports System.IO
Imports System.Linq



Namespace Aricie.DNN.Services.Url.FriendlyUrl

    Public Enum GroupProviderTypes
        ConstantUrlGroup
        AliasUrlGroup
        ProviderUrlGroup
        QueryStringUrlGroup
        UrlPartGroup
    End Enum

    <Serializable()> _
    Public Class FriendlierUrlRule
        Inherits ProviderHost(Of DotNetType(Of UrlGroup), UrlGroup, IGenericizer(Of UrlGroup))




        Private Const REWRITE_GROUP_REGEX As String = "(?<Group>" _
                                                & "\[(?<GroupPrefix>[^\[\]]*)?" _
                                                & _
                                                "(?<GroupReplace>\${(?<GroupName>[^}\,]+)(?:\,(?<GroupRewriteType>\w+))?(?:\,%(?<GroupRegex>[^\,]+)%)?})" _
                                                & "(?<GroupSuffix>[^\[\]]*)?\]" _
                                                & "(?<InterGroup>[^\[\]{}]*)?)"

        Private Const glbRegexPatternUrl As String = "(?<Groups>(?<GroupNonRegex>(?<!\$¤)[^¤]+)(?:\$¤(?<GroupRegexExp>[^¤]+)¤))+(?<LastItem>(?<!\$¤).+)?"


        Private Const ALIAS_NAME_VALIDATION_REGEX As String = "[a-zA-Z0-9_-]+"
        Friend Const ALIAS_VALIDATION_REGEX As String = "\${(?<AliasName>" & ALIAS_NAME_VALIDATION_REGEX & ")}"

        Public Const REWRITE_RULE_VALIDATION_REGEX As String = "^(?:(?:" & REWRITE_GROUP_REGEX & "|" & ALIAS_VALIDATION_REGEX & ")+|DNN)$"


        Private Shared _GroupRegex As New Regex(REWRITE_GROUP_REGEX, RegexOptions.IgnoreCase Or RegexOptions.Singleline)

        Private _OrderedGroups As List(Of UrlGroup)

        Public Overrides Function GetAvailableProviders() As System.Collections.Generic.IDictionary(Of String, DotNetType(Of UrlGroup))
            Dim toReturn As New Dictionary(Of String, DotNetType(Of UrlGroup))
            toReturn.Add(GroupProviderTypes.UrlPartGroup.ToString, New DotNetType(Of UrlGroup)(New DotNetType(GetType(UrlPartGroup))))
            toReturn.Add(GroupProviderTypes.QueryStringUrlGroup.ToString, New DotNetType(Of UrlGroup)(New DotNetType(GetType(QueryStringUrlGroup))))
            toReturn.Add(GroupProviderTypes.AliasUrlGroup.ToString, New DotNetType(Of UrlGroup)(New DotNetType(GetType(AliasUrlGroup))))
            toReturn.Add(GroupProviderTypes.ProviderUrlGroup.ToString, New DotNetType(Of UrlGroup)(New DotNetType(GetType(ProviderUrlGroup))))
            toReturn.Add(GroupProviderTypes.ConstantUrlGroup.ToString, New DotNetType(Of UrlGroup)(New DotNetType(GetType(ConstantUrlGroup))))
            Return toReturn
        End Function

        Private Shared _VariablePatternRegex As Regex
        Private Shared _VariablePatternRegexLock As New Object

        Private Shared ReadOnly Property VariablePatternRegex() As Regex
            Get
                If _VariablePatternRegex Is Nothing Then
                    SyncLock _VariablePatternRegexLock
                        If _VariablePatternRegex Is Nothing Then
                            _VariablePatternRegex = New Regex(glbRegexPatternUrl, RegexOptions.Compiled Or RegexOptions.CultureInvariant)
                        End If
                    End SyncLock
                End If
                Return _VariablePatternRegex
            End Get
        End Property

        Private _orderSync As New Object

        Private ReadOnly Property OrderedGroups() As IList(Of UrlGroup)
            Get
                If _OrderedGroups Is Nothing Then
                    SyncLock _orderSync
                        If _OrderedGroups Is Nothing Then
                            Dim availables As New List(Of UrlGroup)(Me.Instances.Where(Function(objGroup) objGroup.Enabled))
                            Dim tempDico As New SortedDictionary(Of Integer, List(Of UrlGroup))
                            Dim tempUrlgroup As UrlGroup = Nothing
                            Dim tempPriorityQueue As List(Of UrlGroup) = Nothing
                            For i As Integer = 0 To availables.Count - 1
                                tempUrlgroup = availables(i)
                                If Not tempDico.TryGetValue(tempUrlgroup.ComputeOrder, tempPriorityQueue) Then
                                    tempPriorityQueue = New List(Of UrlGroup)
                                    tempDico.Add(tempUrlgroup.ComputeOrder, tempPriorityQueue)
                                End If
                                tempPriorityQueue.Add(tempUrlgroup)
                            Next
                            Dim objGroups As New List(Of UrlGroup)(availables.Count)
                            For Each priority As Integer In tempDico.Keys
                                tempPriorityQueue = tempDico(priority)
                                objGroups.AddRange(tempPriorityQueue)
                            Next
                            _OrderedGroups = objGroups
                        End If
                    End SyncLock
                End If
                Return _OrderedGroups
            End Get
        End Property

        Protected Overrides Sub OnCollectionChange()
            If _OrderedGroups IsNot Nothing Then
                SyncLock _orderSync
                    _OrderedGroups = Nothing
                End SyncLock
            End If
        End Sub

       
        Public Function GetRewrite(ByVal writer As UrlRewriteContext) As VariableRewriteResult
            Dim toReturn As New VariableRewriteResult
            Dim mainUrlBuilder As New StringBuilder
            Dim varUrlBuilder As New StringBuilder
            Dim visible As Boolean
            Dim isRegex As Boolean = False

            Dim orderedRewrites As New Dictionary(Of UrlGroup, GroupRewriteResult)

            For Each objGroup As UrlGroup In Me.OrderedGroups
                If objGroup.Enabled Then
                    If objGroup.Visibility = UrlGroupVisibility.MasterDuplicate Then
                        Dim masterGroupResult As GroupRewriteResult = Nothing
                        'If writer.GroupRewrites.TryGetValue(objGroup.MasterGroupName, masterGroupResult) Then
                        '    'todo: deal with mastergroup conditional visibility

                        'End If
                    Else
                        visible = True
                        'no extension if blank domainname
                        'If TypeOf (objGroup) Is UrlPartGroup AndAlso DirectCast(objGroup, UrlPartGroup).UrlPartName = UrlPart.Extension Then
                        '    Dim currentUrl As String = mainUrlBuilder.ToString
                        '    If currentUrl.Length <= 8 OrElse currentUrl.Substring(8).TrimEnd("/"c).IndexOf("/"c) <= 0 Then
                        '        visible = False
                        '    End If
                        'End If
                    End If
                    If visible Then
                        Dim rewrite As GroupRewriteResult = objGroup.GetRewrite(writer)
                        writer.RegisterRewrite(objGroup, rewrite)
                        orderedRewrites(objGroup) = rewrite
                    End If
                End If
            Next

            'the display order may differ from the computed order
            For Each objGroup As UrlGroup In Me.Instances
                If objGroup.Enabled Then
                    Dim tempResult As GroupRewriteResult = Nothing
                    If orderedRewrites.TryGetValue(objGroup, tempResult) Then
                        ' we don't want to call the context rewriting method once again, because it would filter the content twice
                        Dim currentGroupValue As String = tempResult.RetrieveContent(objGroup.GetSeparationChar, objGroup.GetParamValueChar)
                        mainUrlBuilder.Append(currentGroupValue)
                        If Not String.IsNullOrEmpty(tempResult.RewriteRegex) Then
                            isRegex = True
                            varUrlBuilder.Append(tempResult.RewriteRegex)
                        Else
                            varUrlBuilder.Append(currentGroupValue)
                        End If
                    End If
                End If
            Next

           
            toReturn.Url = mainUrlBuilder.ToString

            'Dealing with hacks
            For Each hack As KeyValuePair(Of String, Regex) In FriendlierUrlContext.Hacks
                If hack.Value.IsMatch(toReturn.Url) Then
                    isRegex = True
                    varUrlBuilder = New StringBuilder(hack.Value.Replace(varUrlBuilder.ToString, FriendlierUrlRule.FormatRegexPattern(".*")))
                    'breaks the admin keyfield modules
                    'If Not toReturn.Url.Contains(hack.Key) Then
                    '    'probably because of lower case string espcaping (the regex just matched)
                    '    toReturn.Url = hack.Value.Replace(toReturn.Url, hack.Key)
                    'End If
                End If
            Next


            If isRegex Then
                toReturn.RawRegex = varUrlBuilder.ToString()
                toReturn.Pattern = Me.GetVariablePattern(varUrlBuilder.ToString)
            Else
                toReturn.Pattern = ""
            End If
            toReturn.IsRewritten = True
            Return toReturn
        End Function

        Public Function GetPartialRewrite(ByVal writer As UrlRewriteContext) As GroupRewriteResult

            Dim varResult As VariableRewriteResult = Me.GetRewrite(writer)
            Dim toReturn As New GroupRewriteResult

            toReturn.RewriteValue = varResult.Url
            if Not varResult.RawRegex.IsNullOrEmpty()
                toReturn.RewriteRegex = varResult.RawRegex
            End If

            Return toReturn
        End Function

        
        Public Function GetGroupProviders() As List(Of UrlProviderInfo)
            Dim providerNames As New List(Of String)
            Me.GetGroupProvidersInternal(providerNames)

            Dim toReturn As New List(Of UrlProviderInfo)
            For Each name As String In providerNames
                If FriendlierUrlConfig.Instance.AvailableUrlrewriters.ContainsKey(name) Then
                    toReturn.Add(FriendlierUrlConfig.Instance.AvailableUrlrewriters(name))
                End If
            Next
            Return toReturn

        End Function

        Private Sub GetGroupProvidersInternal(ByRef providerNames As List(Of String))
            If Me.Instances IsNot Nothing Then
                For Each group As UrlGroup In Me.Instances

                    Dim providerGroup As ProviderUrlGroup = TryCast(group, ProviderUrlGroup)
                    If providerGroup IsNot Nothing Then
                        If Not providerNames.Contains(providerGroup.UrlProviderName) Then
                            providerNames.Add(providerGroup.UrlProviderName)
                        End If
                    Else
                        Dim aliasGroup As AliasUrlGroup = TryCast(group, AliasUrlGroup)
                        If aliasGroup IsNot Nothing Then
                            If aliasGroup.AliasRule IsNot Nothing Then
                                aliasGroup.AliasRule.GetGroupProvidersInternal(providerNames)
                            End If
                        End If
                    End If
                Next
            End If
        End Sub


        Public Shared Function FormatRegexPattern(ByVal strRegex As String) As String
            Return String.Format("$¤{0}¤", strRegex)
        End Function


        Public Overrides Function ToString() As String
            Dim toReturn As String = String.Empty
            For Each objGroup As UrlGroup In Me.Instances
                toReturn &= objGroup.ToString()
            Next
            Return toReturn
        End Function

#Region "Private Methods"




        Private Function GetVariablePattern(ByVal enginePattern As String) As String

            'Use a Regex to extract the fixed and variable parts from the engine pattern
            Dim varPatternMatch As Match = VariablePatternRegex.Match(enginePattern)
            If Not varPatternMatch.Success Then
                Throw New InvalidDataException("Wrong Rewrite Pattern: " & enginePattern)
            End If
            'variable pattern found, building the actual regex that corresponds to the pattern
            Dim toReturn As New System.Text.StringBuilder
            Dim prefixs As CaptureCollection = varPatternMatch.Groups("GroupNonRegex").Captures
            Dim regexs As CaptureCollection = varPatternMatch.Groups("GroupRegexExp").Captures
            If prefixs.Count <> regexs.Count Then
                Throw New InvalidOperationException("Impossible to match regex variable url: " & enginePattern)
            End If
            For i As Integer = 0 To prefixs.Count - 1

                'Insert a non-capturing group for the fixed part
                toReturn.Append("(?:")
                toReturn.Append(Regex.Escape(prefixs(i).Value))
                'Finish fixed group and insert a named group for the variable part
                toReturn.Append(")(")
                toReturn.Append(regexs(i).Value)
                toReturn.Append(")")

            Next
            'do we have a final fixed part?
            Dim lastItem As String = varPatternMatch.Groups("LastItem").Value
            If Not String.IsNullOrEmpty(lastItem) Then
                'including the last fixed part
                toReturn.Append("(?:")
                toReturn.Append(Regex.Escape(lastItem))
                toReturn.Append(")")
            End If
            Return toReturn.ToString
        End Function
#End Region



    End Class

    Public Enum AliasOrRule
        [Alias]
        Rule
    End Enum

End Namespace
